﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Parsing
{
    class Parser
    {
        public class ParsetExeption : ApplicationException
        {
            public ParsetExeption(string str) : base(str) { }
            public override string ToString()
            {
                return Message;
            }
        }
        
        enum Types { NONE, DELIMITER, VARIABLE, NUMBER,FUNCTION };
        enum Errors{SYNTAX,UNBALPARENS,NOEXP,DIVBYZERO};
        
        string expression; // вырожение
        int expIdx; // текущий индекс выражения
        string token; // текущая лексема
        Types tokType; // Тип лексемы

        // вычисляем значение мат функции
        public double Evaluate(string expstr)
        {
            double result;
            expression = expstr;
            expIdx = 0;
           // try
           // {
                GetToken();
                if (token == "")
                {
                    SyntaxErr(Errors.NOEXP); // expression is none!
                }
                EvalExp2(out result);
                if (token != "") // The last lexema must be null!
                {
                    SyntaxErr(Errors.SYNTAX);
                }
                return result;
          //  }
           // catch (ParsetExeption exc)
           // {
                
            //    return 0.0;
           // }
        }
        // обработка + или минус
        void EvalExp2(out double result)
        {
            string op;
            double partialResult;
            EvalExp3(out result);
            while ((op = token) == "+" || op == "-")
            {
                GetToken();
                EvalExp3(out partialResult);
                switch (op)
                {
                    case "-": result = result - partialResult; break;
                    case "+": result = result + partialResult; break;
                }
            }
        }
        // обработка * или / или %
        void EvalExp3(out double result)
        {
            string op;
            double partialResult = 0.0;
            EvalExp4(out result);
            while ((op = token) == "*" || op == "/" || op == "%")
            {
                GetToken();
                EvalExp4(out partialResult);
                switch (op)
                {
                    case "*": result = result * partialResult; break;
                    case "/": result = result / partialResult; break;
                    case "%": result = result % partialResult; break;
                }
            }
        }
        // обработка ^
        void EvalExp4(out double result)
        {
            double partialResult, ex;
            EvalExp5(out result);
            if (token == "^")
            {
                GetToken();
                EvalExp4(out partialResult);
                ex = result;
                if (partialResult == 0.0)
                {
                    result = 1.0;
                    return;
                }

                    result = Math.Pow(ex, partialResult);

            }
        }
        // обработка унарного  -
        void EvalExp5(out double result)
        {
            string op = "";
            
            if ((tokType == Types.DELIMITER) && token =="+" || token == "-")
            {
                op = token;
                GetToken();
            }
            EvalExp6(out result);
            if (op == "-") result = -result;
        }
        void EvalExp6(out double result)
        {

            if (token == "cos" || token == "sin" || token == "tg" || token == "ctg" || token == "sqrt" || token == "acos" || token == "asin" || token == "sqr" || token == "e" || token == "ln")
            {
                string op = token;
                GetToken();
                double partialResult;
                EvalExp7(out partialResult);
                
                switch (op)
                {
                    case "cos": result = Math.Cos(partialResult); break;
                    case "sin": result = Math.Sin(partialResult); break;
                    case "tg": result = Math.Tan(partialResult); break;
                    case "ctg": result = Math.Cos(partialResult) / Math.Sin(partialResult); break;
                    case "acos": result = Math.Acos(partialResult); break;
                    case "asin": result = Math.Asin(partialResult); break;
                    case "sqr": result = partialResult * partialResult; break;
                    case "ln": result = Math.Log(partialResult); break;
                    case "e": result = Math.Exp(partialResult); break;
                    case "sqrt": if (partialResult < 0) { result = 0.0; SyntaxErr(Errors.SYNTAX); break; } result = Math.Sqrt(partialResult); break;
                    default:
                        result = 0.0;
                        SyntaxErr(Errors.SYNTAX);
                        break;
                }
            }
            else
            {
                EvalExp7(out result);
            }
            
            

        }
        // обработка (
        void EvalExp7(out double result)
        {
            if ((token == "("))
            {
                GetToken();
                EvalExp2(out result);
                if (token != ")")
                    SyntaxErr(Errors.UNBALPARENS);
                GetToken();
            }
            else
            {
                Atom(out result);
            }
        }
        
        void Atom(out double result)
        {
            switch (tokType)
            {
                case Types.NUMBER:
                    try
                    {
                        result = Double.Parse(token);
                                                
                    }
                    catch (FormatException)
                    {
                        result = 0.0;
                        SyntaxErr(Errors.SYNTAX);
                    }
                    GetToken();
                    return;

                default:
                    result = 0.0;
                    SyntaxErr(Errors.SYNTAX);
                    break;
            }
        }
        // обработка исключений
        void SyntaxErr(Errors error)
        {
            string[] err = { "Синтаскическая ошибка", "Дисбаланс скобок", "Выражение отсутствует", "Деление на нуль" };
            throw new ParsetExeption(err[(int)error]);
        }
        public void GetToken()
        {
            tokType = Types.NONE;
            token = "";
            if (expIdx == expression.Length) return; // THE END of Expression;
            while (expIdx < expression.Length && Char.IsWhiteSpace(expression[expIdx])) ++expIdx; // пропускаем пробелы
            if (expIdx == expression.Length) return; // THE END of Expression;
            if (IsDelime(expression[expIdx])) // This is operator?
            {
                token += expression[expIdx];
                expIdx++;
                tokType = Types.DELIMITER;
            }
            else
            {
                if (Char.IsLetter(expression[expIdx])) // This is variable ?
                {
                    while (!IsDelime(expression[expIdx]))
                    {
                        token += expression[expIdx];
                        expIdx++;
                        if (expIdx >= expression.Length) break;
                    }
                   // if (IsFunct(token))
                    //    tokType = Types.FUNCTION;
                  //  else
                        tokType = Types.VARIABLE;
                }
                else
                {
                    if (Char.IsDigit(expression[expIdx])) // This is Number?
                    {
                        while (!IsDelime(expression[expIdx]))
                        {
                            token += expression[expIdx];
                            expIdx++;
                            if (expIdx >= expression.Length) break;
                        }
                        tokType = Types.NUMBER;
                    }
                }
            }

        }
        bool IsDelime(char c)
        {
            if ((" +-/*%^=()".IndexOf(c) != -1))
                return true;
            else return false;
        }
       

       
    }
}
